/*
 * Customers.cpp
 *
 *  Created on: 2011-10-30
 *      Author: mateusz
 */

#include "Customers.h"
#include "NoMatchingCustomer.cpp"
#include "CVRP.h"

using namespace std;

Customers::Customers() {
	this->unvisitedCount = 0; // number of unvisited customers
	this->depotDistancesLeft = 0; // sum of all depot distances from unvisited customers
	this->minEdgesLeft = 0; // minimum route (edge) incident with some unvisited customer
	this->totalDemandLeft = 0;
}

Customers::~Customers() {
	// delete all customers
	Customer* c;
	while (!(this->customers).empty()) {
		c = (this->customers).back();
		(this->customers).pop_back();
		delete c;
	}
}

/**
 * add customer to set of customers;
 * increase counters
 */
void Customers::addCustomer(int id, double x, double y, int demand) {
	Customer* c = new Customer(id, x, y, demand);
	(this->customers).push_back(c);
	this->unvisitedCount++;
	this->depotDistancesLeft += c->getDepotDistance();
	this->totalDemandLeft += c->getDemand();
}

Customer* Customers::getCustomer(int id) {
	return this->customers[id];
}

int Customers::getCustomerInputId(int id) {
	return this->customers[id]->getInputId();
}

/**
 * get next unvisited customer (order by id)
 */
Customer* Customers::getNextUnvisited(int startFrom = 0, int maxDemand = INFTY) {
	startFrom = max(startFrom, 0);
	for (int i = startFrom; i < (signed) this->customers.size(); ++i) {
		if (!this->customers[i]->isVisited() && this->customers[i]->getDemand() <= maxDemand) {
			return this->customers[i];
		}
	}

	// throw exception if any of customers match
	throw NoMatchingCustomer();
}

int Customers::getTotalCustomers() {
	return (this->customers).size();
}

/**
 * visit customer
 * decrease some counters (such as univisited count, depot distances left)
 */

void Customers::visitCustomer(int id) {
	Customer* c = this->customers[id];
	c->visit();
	this->unvisitedCount--;
	this->depotDistancesLeft -= c->getDepotDistance();

	// find in set of min vertex distances occurence of pair (best distance for id, id)
	sit = (this->minVertexDistances).find(make_pair(c->getMinVertexDistance(), id));
	// erase it from set
	(this->minVertexDistances).erase(sit);

	// decrease sum of miminum edges left incident with unvisited customers
	this->minEdgesLeft -= c->getMinVertexDistance();

	// decrease demand left
	this->totalDemandLeft -= c->getDemand();
}

void Customers::unvisitCustomer(int id) {
	Customer* c = this->customers[id];
	c->unvisit();
	this->unvisitedCount++;
	this->depotDistancesLeft += c->getDepotDistance();

	// insert pair (best distance for id, id) to set of min vertex distances
	(this->minVertexDistances).insert(make_pair(c->getMinVertexDistance(), id));

	// add min neighbor distance from this vertex
	this->minEdgesLeft += c->getMinVertexDistance();

	// increase demand left
	this->totalDemandLeft += c->getDemand();
}

int Customers::getTotalUnvisitedCustomers() {
	return this->unvisitedCount;
}

double Customers::getDepotDistancesLeft() {
	return this->depotDistancesLeft;
}

int Customers::getTotalDemandLeft() {
	return this->totalDemandLeft;
}

/**
 * returns depot distance from customer with id == id
 */
double Customers::getDepotDistance(int id) {
	return this->customers[id]->getDepotDistance();
}

/**
 * compare function to sort customers
 * sort by depot distance
 * returns true if customer a is closer depot than customer b 1
 */
bool cmpFn(Customer* a, Customer* b) {
	return (a->getDepotDistance() < b->getDepotDistance());
}

bool cmpFnAngle(Customer* a, Customer* b) {
	if(a->getDepotAngle() < b->getDepotAngle()){
		return true;
	}
	else if(a->getDepotAngle() > b->getDepotAngle()){
		return false;
	}

	return a->getDepotDistance() < b->getDepotDistance();
}

/**
 * preprocessing of customers
 */
void Customers::preprocessing() {
	double bestDistance;
	pair <int, int> locationI;
	int customersSize = (signed) (this->customers).size();

	// sort customers vector using cmpFn compare function
	sort((this->customers).begin(), (this->customers).end(), cmpFnAngle);

	// set id of customer (results same index in vector and id)
	for (int i = 0; i < customersSize; i++) {
		//printf("i = %d, customerId = %d, depotAngle = %lf\n", i, this->customers[i]->getInputId(), this->customers[i]->getDepotAngle());
		this->customers[i]->setId(i);
	}

	// calculate min neighbor vertex distances from each vertex
	for (int i = 0; i < customersSize; i++) {
		// set first best distance to depot distance
		bestDistance = this->customers[i]->getDepotDistance();

		locationI = this->customers[i]->getXY();

		// calculate distance to every other customer
		for (int j = 0; j < customersSize; j++) {
			if (i != j) {
				bestDistance = min(bestDistance, CVRP::distance(locationI, this->customers[j]->getXY()));
			}
		}

		// insert pair (distance, id) to set
		(this->minVertexDistances).insert(make_pair(bestDistance, i));

		// assign bestDistance to customer
		this->customers[i]->setMinVertexDistance(bestDistance);

		// increase sum of min edges incident with all customers by best distance to other from customer i
		this->minEdgesLeft += bestDistance;
	}

}

/**
 * returns best min neighbor distance from unvisited customers
 */
double Customers::getMinVertexDistance() {
	// return first value of pair at the begining of the set
	return (this->minVertexDistances).begin()->first;
}

/**
 * return sum of all minimum edges incident with each unvisited customer
 */
double Customers::getMinEdgesLeft() {
	return this->minEdgesLeft;
}

